package com.mimo.common.configuration.limit.aspect;

import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;

import com.mimo.common.configuration.limit.annotation.AccessLimit;
import com.mimo.common.utils.ReflectionUtils;

@Aspect
public class AccessLimitAspect implements Ordered, IAccessLimitService {
  private static Map<Class<?>, RuntimeException> exceptionHolder = new ConcurrentHashMap<>();

  @Autowired
  private RedisTemplate<String, String> redisTemplate;

  @Resource(name = "accessLimitScript")
  private RedisScript<Boolean> accessLimitScript;

  @Around(value = "@annotation(annotation)")
  public Object around(ProceedingJoinPoint joinPoint, AccessLimit annotation) throws Throwable {
    String key = extract(annotation.key(), joinPoint);

    long expireInMill = TimeUnit.MILLISECONDS.convert(annotation.expire(), annotation.timeUnit());

    if (!this.accessIncr(key, annotation.count(), expireInMill)) {
      RuntimeException e = exceptionHolder.get(annotation.exception());
      if (Objects.isNull(e)) {
        e = ReflectionUtils.newInstanceOf(annotation.exception());
        exceptionHolder.putIfAbsent(annotation.exception(), e);
      }
      throw e;
    }

    return joinPoint.proceed();
  }

  /**
   * 新增用于处理Key的SPEL解析
   * 
   * @param key
   * @param joinPoint
   * @return 返回解析后的Key
   */
  private String extract(String key, ProceedingJoinPoint joinPoint) {
    String parsedKey = key;
    if (key.contains(ParserContext.TEMPLATE_EXPRESSION.getExpressionPrefix())) { // 如果是SPEL表达式,则需要做SPEL解析
      MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
      String[] parameters = methodSignature.getParameterNames();

      EvaluationContext context = new StandardEvaluationContext();
      for (int pIndex = 0; pIndex < parameters.length; pIndex++) {
        context.setVariable(parameters[pIndex], joinPoint.getArgs()[pIndex]);
      }
      parsedKey = new SpelExpressionParser().parseExpression(key, new TemplateParserContext()).getValue(context,
          String.class);
    }
    return parsedKey;
  }

  @Override
  public boolean accessIncr(String key, int count, long expireInMill) {
    Assert.isTrue(expireInMill > 100, "锁定期至少大于100ms");
    Assert.isTrue(count > 0, "访问次数至少大于0");
    return redisTemplate.execute(accessLimitScript, Arrays.asList(key), String.valueOf(count),
        String.valueOf(expireInMill));
  }

  @Override
  public int getOrder() {
    return 0;
  }

}
